#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "launch.h"
#include <QSettings>
#include <QFileDialog>
#include <QStandardPaths>
#include <QTextCodec>
#include <QMessageBox>
#include <QLabel>
#include <QCryptographicHash>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->statusBar->addPermanentWidget(new QLabel("Ver:"+QString(__DATE__ "_" __TIME__)));
    ui->progressBar->setStyleSheet("QProgressBar::chunk { background-color: #10ff10; width: 20px;}");

    // 获取配置文件中板子的名称，更新到ui
    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));

    configFile.beginGroup("Global");
    factoryMode = configFile.value("factory").toBool();
    QString lastBoard=configFile.value("last_selected_board").toString();
    configFile.endGroup();

    QStringList sections = configFile.childGroups();
    foreach(QString item, sections)
    {
        if (item.endsWith("Board"))
        {
            configFile.beginGroup(item);
            QString boardName = configFile.value("NAME").toString();
            configFile.endGroup();

            ui->boardComboBox->addItem(boardName);
        }
    }

    if (!lastBoard.isEmpty())
    {
        ui->boardComboBox->setCurrentText(lastBoard);
    }

    /* 生成update flag文件 */
    QFile needUpdateFlag(firmwareDir + curNeedUpdateFlag);
    needUpdateFlag.open(QIODevice::WriteOnly | QIODevice::Truncate);
    QDataStream out(&needUpdateFlag);
    out << static_cast<qint64>(0xffffffffb66bb66b);
    needUpdateFlag.close();

    QFile updateFlag(firmwareDir + curUpdateFlag);
    updateFlag.open(QIODevice::WriteOnly | QIODevice::Truncate);
    QDataStream out1(&updateFlag);
    out1 << static_cast<qint64>(0xc77cc77cb66bb66b);
    updateFlag.close();

    ui->imageLabel->setHidden(true);
    if (factoryMode) // 工厂模式
    {
        /* 不该显示的按钮藏起来 */
        ui->burnIFlashPushButton->setHidden(true);
        ui->burnEFlashPushButton->setHidden(true);
        ui->eraseIFlashPushButton->setHidden(true);
        ui->eraseEFlashPushButton->setHidden(true);

        /* 修改刷机按钮样式 */
        ui->burnAllPushButton->setFixedSize(522, 34);
        ui->burnAllPushButton->setText("烧写程序");

        /* 用图片盖住各选项 */
        ui->imageLabel->raise();
        ui->imageLabel->setHidden(false);

        Launch la;
        la.exec();
        ui->boardComboBox->setCurrentText(la.getBoardName());
    }
}

MainWindow::~MainWindow()
{
    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    configFile.beginGroup("Global");
    configFile.setValue("last_selected_board", ui->boardComboBox->currentText());
    configFile.endGroup();

    QFile(firmwareDir + defaultBkpPara).remove();
    QFile(firmwareDir + curUpdateFlag).remove();
    QFile(firmwareDir + curNeedUpdateFlag).remove();

    delete ui;
}

QString MainWindow::findSectionByName(const QString &boardName)
{
    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    QStringList sections = configFile.childGroups();
    foreach (QString item, sections)
    {
        configFile.beginGroup(item);
        if (configFile.value("NAME").toString() == boardName)
        {
            configFile.endGroup();
            return item;
        }
        configFile.endGroup();
    }

    return nullptr;
}

QString MainWindow::findKeyByValue(const QString &section, const QString &value)
{
    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    configFile.beginGroup(section);
    QStringList keyList = configFile.allKeys();
    foreach(QString key, keyList)
    {
        if (configFile.value(key).toString() == value)
        {
            return key;
        }
    }
    configFile.endGroup();

    return nullptr;
}

int MainWindow::burnExternFlash(QString filePath, QString loaderPath, QString offset)
{
    QStringList param;
    param.append("-P");
    param.append(filePath);
    param.append(offset);
    param.append("-V");
    param.append("after_programming");
    param.append("-EL");
    param.append(loaderPath);
    param.append("-Rst");
    param.append("-Q");

    cliProcessInternal.start(stlinkPath, param);
    cliProcessInternal.waitForStarted();
    cliProcessInternal.waitForFinished(15000);

    return judgeConnectionError(cliProcessInternal.readAll());
}

int MainWindow::eraseExternFlash(QString loaderPath)
{
    QStringList param;
    param.append("-c");
    param.append("SWD");
    param.append("UR");
    param.append("-ME");
    param.append("-EL");
    param.append(loaderPath);
    param.append("-Rst");
    param.append("-Q");

    cliProcessInternal.start(stlinkPath, param);
    cliProcessInternal.waitForStarted();
    cliProcessInternal.waitForFinished(15000);

    return judgeConnectionError(cliProcessInternal.readAll());
}

int MainWindow::burnInternalFlash(QString filePath, QString offset)
{
    QStringList param;
    param.append("-c");
    param.append("SWD");
    param.append("UR");
    param.append("-P");
    param.append(filePath);
    param.append(offset);
    param.append("-V");
    param.append("after_programming");
    param.append("-Rst");
    param.append("-Q");

    cliProcessInternal.start(stlinkPath, param);
    cliProcessInternal.waitForStarted();
    cliProcessInternal.waitForFinished(15000);

    return judgeConnectionError(cliProcessInternal.readAll());
}

int MainWindow::eraseInternalFlash()
{
    QStringList param;
    param.append("-c");
    param.append("SWD");
    param.append("UR");
    param.append("-ME");
    param.append("-Rst");
    param.append("-Q");

    cliProcessInternal.start(stlinkPath, param);
    cliProcessInternal.waitForStarted();
    cliProcessInternal.waitForFinished(15000);

    return judgeConnectionError(cliProcessInternal.readAll());
}

int MainWindow::judgeConnectionError(QString res)
{
    int ret;

    if (res.contains("MCU Reset") &&
            (res.contains("Programming Complete") ||
             res.contains("Flash memory erased") ||
             res.contains("External Memory full erase")))
    {
        ret = 0;
    }
    else if (res.contains("No target connected"))
    {
        QMessageBox::critical(this, "USB错误", "请检查ST-Link到PCB之间的连接线。\r\n或者重新拔插ST-Link的USB再试一次。");
        ret = -1;
    }
    else if (res.contains("No ST-LINK detected") || res.contains("Unable to connect to ST-LINK"))
    {
        QMessageBox::critical(this, "SWD错误", "请检查电脑到ST-Link之间的连接线。");
        ret = -2;
    }
    else if (res.contains("Timeout during flash programming") || res.contains("External Memory full erase"))
    {
        QMessageBox::critical(this, "超时", "芯片选择有误。");
        ret = -3;
    }
    else if (res.contains("Unable to open file"))
    {
        QMessageBox::critical(this, "文件错误", "固件文件丢失，或者配置文件不对。");
        ret = -3;
    }
    else
    {
        QMessageBox::critical(this, "未知错误", res);
        ret = -100;
    }

    if (ret != 0)
    {
        ui->progressBar->setValue(0);
    }

    return ret;
}

qint64 MainWindow::convertQIntEndian(qint64 src)
{
    qint64 dest = 0;

    for (int i = 0; i < 8; i++) {
        dest <<= 8;
        dest |= src & 0xff;
        src >>= 8;
    }

    return dest;
}

qint32 MainWindow::convertQIntEndian(qint32 src)
{
    qint32 dest = 0;

    for (int i = 0; i < 4; i++) {
        dest <<= 8;
        dest |= src & 0xff;
        src >>= 8;
    }

    return dest;
}

void MainWindow::on_boardComboBox_currentTextChanged(const QString &arg1)
{
    ui->socComboBox->clear();

    setWindowTitle("厨芯：" + arg1);
    curSection = findSectionByName(arg1);

    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    configFile.beginGroup(curSection);
    QStringList keyList = configFile.allKeys();
    foreach(QString key, keyList)
    {
        if (key.contains("SOC"))
        {
            ui->socComboBox->addItem(configFile.value(key).toString());
        }
    }

    /* 显示电路板图片，只有工厂模式才会显示，否则隐藏 */
    QString boardImage = configFile.value("IMG").toString();
    ui->imageLabel->setPixmap(QPixmap(boardImagePath + boardImage).scaled(ui->imageLabel->size()));

    /* 显示烧写计数 */
    unsigned int burnCount = configFile.value("BURN_COUNT").toUInt();
    ui->countLabel->setText("已成功烧写：" + QString::number(burnCount));

    /* 显示板子注释信息 */
    ui->commitLabel->setText(configFile.value("COMMIT").toString());

    configFile.endGroup();
}

void MainWindow::on_socComboBox_currentTextChanged(const QString &arg1)
{
    ui->bootComboBox->clear();
    ui->appComboBox->clear();
    ui->extLoaderLabel->clear();

    curSoc = arg1;

    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    configFile.beginGroup(curSection);
    QStringList keyList = configFile.allKeys();
    foreach(QString key, keyList)
    {
        if (key.startsWith(curSoc + "_BL"))
        {
            ui->bootComboBox->addItem(configFile.value(key).toString());
            ui->bootCheckBox->setChecked(true);
        }

        if (key.startsWith(curSoc + "_APP"))
        {
            ui->appComboBox->addItem(configFile.value(key).toString());
            ui->appCheckBox->setChecked(true);
        }

        if (key.startsWith(curSoc + "_EXLOADER"))
        {
            curExtLoader = configFile.value(key).toString();
            ui->extLoaderLabel->setText("SPI-Flash: " + curExtLoader);
        }
    }
    configFile.endGroup();
}

void MainWindow::on_burnAllPushButton_clicked()
{
    int ret;
    QString pathStr;

    ui->statusBar->showMessage("准备烧录，请稍后...", 3000);
    ui->progressBar->setStyleSheet("QProgressBar::chunk { background-color: #c0c0ff; width: 20px;}"); // 设置进度条颜色
    ui->progressBar->setValue(1);

    if (ui->bootCheckBox->isChecked())
    {
        ui->statusBar->showMessage("正在烧写片内Flash:BootLoader(1/6)", 3000);
        if (curBoot.contains(":/"))
        {
            pathStr = curBoot;
        }
        else
        {
            pathStr = firmwareDir + curBoot;
        }
        ret = burnInternalFlash(pathStr, defaultIFlashBootOffset);
        if (ret != 0)
        {
            return;
        }
    }
    ui->progressBar->setValue(10);

    if (ui->appCheckBox->isChecked())
    {
        ui->statusBar->showMessage("正在烧写片内Flash:Application(2/6)", 3000);
        if (curApp.contains(":/"))
        {
            pathStr = curApp;
        }
        else
        {
            pathStr = firmwareDir + curApp;
        }
        ret = burnInternalFlash(pathStr, defaultIFlashAppOffset);
        if (ret != 0)
        {
            return;
        }

        ui->progressBar->setValue(36);

        ui->statusBar->showMessage("正在烧写片外Flash升级标识区(3/6)", 3000);
        if (curUpdateFlag.contains(":/"))
        {
            pathStr = curUpdateFlag;
        }
        else
        {
            pathStr = firmwareDir + curUpdateFlag;
        }
        ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashUpdateFlagOffset);
        if (ret != 0)
        {
            return;
        }
        ui->progressBar->setValue(41);

        ui->statusBar->showMessage("正在烧写片外Flash待升级标识区(4/6)", 3000);
        if (curNeedUpdateFlag.contains(":/"))
        {
            pathStr = curNeedUpdateFlag;
        }
        else
        {
            pathStr = firmwareDir + curNeedUpdateFlag;
        }
        ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashNeedUpdateFlagOffset);
        if (ret != 0)
        {
            return;
        }
        ui->progressBar->setValue(59);

        ui->statusBar->showMessage("正在烧写片外Flash备份固件参数区(5/6)", 3000);
        if (curBkpPara.contains(":/"))
        {
            pathStr = curBkpPara;
        }
        else
        {
            pathStr = firmwareDir + curBkpPara;
        }
        ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashBkpParaOffset);
        if (ret != 0)
        {
            return;
        }
        ui->progressBar->setValue(78);

        ui->statusBar->showMessage("正在烧写片外Flash备份固件区(6/6)", 3000);
        if (curApp.contains(":/"))
        {
            pathStr = curApp;
        }
        else
        {
            pathStr = firmwareDir + curApp;
        }
        ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashAppOffset);
        if (ret != 0)
        {
            return;
        }
    }
    ui->progressBar->setValue(100);
    ui->statusBar->showMessage("烧录完成。", 5000);

    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    configFile.beginGroup(curSection);
    unsigned int count = configFile.value("BURN_COUNT").toUInt();
    count++;
    configFile.setValue("BURN_COUNT", count);
    configFile.endGroup();
    ui->countLabel->setText("已成功烧写：" + QString::number(count));
    ui->progressBar->setStyleSheet("QProgressBar::chunk { background-color: #a0ffa0; width: 20px;}");

//    QMessageBox::information(this, "成功", "烧录成功！");
//    ui->progressBar->setValue(0);
}

void MainWindow::on_burnIFlashPushButton_clicked()
{
    int ret;
    QString pathStr;

    ui->statusBar->showMessage("正在烧写片内Flash...", 3000);
    ui->progressBar->setValue(1);

    if (ui->bootCheckBox->isChecked())
    {
        ui->statusBar->showMessage("正在烧写片内Flash:BootLoader", 3000);
        if (curBoot.contains(":/"))
        {
            pathStr = curBoot;
        }
        else
        {
            pathStr = firmwareDir + curBoot;
        }
        ret = burnInternalFlash(pathStr, defaultIFlashBootOffset);
        if (ret != 0)
        {
            return;
        }
    }

    ui->progressBar->setValue(49);

    if (ui->appCheckBox->isChecked())
    {
        ui->statusBar->showMessage("正在烧写片内Flash:Application", 3000);
        if (curApp.contains(":/"))
        {
            pathStr = curApp;
        }
        else
        {
            pathStr = firmwareDir + curApp;
        }
        ret = burnInternalFlash(pathStr, defaultIFlashAppOffset);
        if (ret != 0)
        {
            return;
        }
    }

    ui->progressBar->setValue(100);
    ui->statusBar->showMessage("片内Flash烧录完成。", 5000);
}

void MainWindow::on_burnEFlashPushButton_clicked()
{
    int ret;
    QString pathStr;

    if (curApp.isEmpty())
    {
        ui->progressBar->setValue(0);
        ui->statusBar->showMessage("没有应用程序文件可烧写！", 3000);
        return;
    }

    ui->statusBar->showMessage("正在烧写片外Flash升级标识区(1/4)", 3000);
    ui->progressBar->setValue(1);
    if (curUpdateFlag.contains(":/"))
    {
        pathStr = curUpdateFlag;
    }
    else
    {
        pathStr = firmwareDir + curUpdateFlag;
    }
    ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashUpdateFlagOffset);
    if (ret != 0)
    {
        return;
    }

    ui->statusBar->showMessage("正在烧写片外Flash待升级标识区(2/4)", 3000);
    ui->progressBar->setValue(23);
    if (curNeedUpdateFlag.contains(":/"))
    {
        pathStr = curNeedUpdateFlag;
    }
    else
    {
        pathStr = firmwareDir + curNeedUpdateFlag;
    }
    ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashNeedUpdateFlagOffset);
    if (ret != 0)
    {
        return;
    }

    ui->statusBar->showMessage("正在烧写片外Flash备份固件参数区(3/4)", 3000);
    ui->progressBar->setValue(47);
    if (curBkpPara.contains(":/"))
    {
        pathStr = curBkpPara;
    }
    else
    {
        pathStr = firmwareDir + curBkpPara;
    }
    ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashBkpParaOffset);
    if (ret != 0)
    {
        return;
    }

    ui->statusBar->showMessage("正在烧写片外Flash备份固件区(4/4)", 3000);
    ui->progressBar->setValue(68);
    if (curApp.contains(":/"))
    {
        pathStr = curApp;
    }
    else
    {
        pathStr = firmwareDir + curApp;
    }
    ret = burnExternFlash(pathStr, extLoaderDir + curExtLoader, defaultExtFlashAppOffset);
    if (ret != 0)
    {
        return;
    }
    ui->progressBar->setValue(100);
    ui->statusBar->showMessage("片外Flash烧录完成。", 5000);
}

void MainWindow::on_eraseIFlashPushButton_clicked()
{
    int ret;

    ui->statusBar->showMessage("正在擦除片内Flash", 1000);
    ui->progressBar->setValue(87);
    ret = eraseInternalFlash();
    if (ret != 0)
    {
        return;
    }
    ui->progressBar->setValue(100);
    ui->statusBar->showMessage("擦除片内Flash完成", 5000);
}

void MainWindow::on_eraseEFlashPushButton_clicked()
{
    int ret;

    ui->statusBar->showMessage("正在擦除片外Flash，大约需要12秒", 12000);
    ui->progressBar->setValue(15);
    ret = eraseExternFlash(extLoaderDir + curExtLoader);
    if (ret != 0)
    {
        return;
    }
    ui->progressBar->setValue(100);
    ui->statusBar->showMessage("擦除片外Flash完成", 5000);
}

void MainWindow::on_bootComboBox_currentTextChanged(const QString &arg1)
{
    curBoot = arg1;
    if (curBoot.isEmpty())
        ui->bootCheckBox->setChecked(false);
}

void MainWindow::on_appComboBox_currentTextChanged(const QString &arg1)
{
    curApp = arg1;
    if (curApp.isEmpty())
        ui->appCheckBox->setChecked(false);

    curBkpPara = defaultBkpPara;

    QString filePath;
    if (curApp.contains(":/"))
    {
        filePath = curApp;
    }
    else {
        filePath = firmwareDir + curApp;
    }
    QFile binFile(filePath);
    if (binFile.open(QIODevice::ReadOnly))
    {
        QByteArray binData = QCryptographicHash::hash(binFile.readAll(), QCryptographicHash::Md5);
        binFile.close();
        QFile args(firmwareDir + defaultBkpPara);
        args.open(QIODevice::WriteOnly | QIODevice::Truncate);
        QDataStream out(&args);
        out << static_cast<qint32>(0);
        out << convertQIntEndian(static_cast<qint32>(binFile.size()));
        args.write(binData.toHex());
        args.close();
    }

    ui->firmwareLabel->setText(curApp);
}

void MainWindow::on_openBootPushButton_clicked()
{
    QString fileName;

    fileName = QFileDialog::getOpenFileName(this, tr("Open file"), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), tr("BootLoader(*.hex *.bin)"));

    if (!fileName.isEmpty()) {
        ui->bootComboBox->setCurrentText(fileName);
        ui->bootCheckBox->setChecked(true);
    }
}

void MainWindow::on_openAppPushButton_clicked()
{
    QString fileName;

    fileName = QFileDialog::getOpenFileName(this, tr("Open file"), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), tr("Application(*.hex *.bin)"));

    if (!fileName.isEmpty()) {
        ui->appComboBox->setCurrentText(fileName);
        ui->appCheckBox->setChecked(true);
    }
}

void MainWindow::on_clearCountPushButton_clicked()
{
    unsigned int count = 0;

    QSettings configFile(configFilePath, QSettings::IniFormat);
    configFile.setIniCodec(QTextCodec::codecForName("UTF-8"));
    configFile.beginGroup(curSection);
    configFile.setValue("BURN_COUNT", count);
    configFile.endGroup();
    ui->countLabel->setText("已成功烧写：" + QString::number(count));
    ui->progressBar->setValue(0);
}
